/*****************************************************\
* Copyright (c) 2020 The game2d Authors. All rights reserved.
  Use of this source code is governed by a BSD-style license that can be
  found in the LICENSE file.

* Author:         dzlua - https://gitee.com/dzlua/game2d.git
* Email:          505544956@qq.com
* FileName:       core/game2d.cc
* Create Time:    2020/03/21 12:17:50
* Description:    /
\****************************************************/
#include "game2d.h"
#include "builder.h"

namespace g2d {
  GameObject::GameObject(bool load_res_on_enter)
      : load_res_on_enter_(load_res_on_enter)
      , layer_(0)
      , parent_(0) {}
  GameObject::~GameObject() { uninit(); }
  Result GameObject::init(Game2D *game, const char *id, Layer *layer) {
    if (!layer) return Result::E_InvalidParam;
    auto rst = Object::init(game, id);
    if (Failed(rst)) return rst;
    layer_ = layer;
    if (!load_res_on_enter_) {
      return load_resource(g2d_->font(), g2d_->texture(), g2d_->audio());
    }
    return Result::S_OK;
  }
  void GameObject::uninit() {
    if (!load_res_on_enter_)
      unload_resource(g2d_->font(), g2d_->texture(), g2d_->audio());
    clear();
  }
  Result GameObject::enter(Font &f, Texture &t, Audio &a) {
    Result rst = Result::S_OK;
    for (auto v : datas_) {
      if (v->load_res_on_enter())
        rst = v->load_resource(f, t, a);
      if (Failed(rst)) break;
      
      rst = v->enter(f, t, a);
      if (Failed(rst)) break;
    }
    return rst;
  }
  Result GameObject::exit(Font &f, Texture &t, Audio &a) {
    Result rst = Result::S_OK;
    for (auto v : datas_) {
      rst = v->exit(f, t, a);
      if (Failed(rst)) break;

      if (v->load_res_on_enter())
        v->unload_resource(f, t, a);
    }
    return rst;
  }
  void GameObject::update(Input &inp, Audio &aud) {
    if (!enabled_) return;
    for (auto v : datas_) {
      v->update(inp, aud);
    }
  }
  void GameObject::render(Renderer &r) {
    if (!visible_) return;
    for (auto v : datas_) {
      v->render(r);
    }
  }
  GameObject* GameObject::find_gobj(const char *id, bool child_first, bool order_rbegin) {
    if (!id || !*id) return 0;
    if (!child_first && id_ == id) return this;
    if (order_rbegin) {
      for (auto it = datas_.rbegin(); it != datas_.rend(); --it) {
        if (!*it) continue;
        auto g = (*it)->find_gobj(id, child_first);
        if (g) return g;
      }
    } else {
      for (auto it = datas_.begin(); it != datas_.end(); ++it) {
        if (!*it) continue;
        auto g = (*it)->find_gobj(id, child_first);
        if (g) return g;
      }
    }
    if (child_first && id_ == id) return this;
    return 0;
  }
  GameObject* GameObject::create_gobj(const char *type, const char *id, Uint32 idx, bool load_res_on_enter) {
    if (!id) return 0;
    auto g = ContainerT<GameObject>::get(id);
    if (g) return g;
    g = g2d_->create_gobj(type, load_res_on_enter);
    auto rst = g->init(g2d_, id, layer_);
    if (Succeeded(rst))
      rst = ContainerT<GameObject>::add(g, idx);
    g->decref();
    return g;
  }
  Result GameObject::add(GameObject *d, Uint32 idx) {
    auto rst = ContainerT<GameObject>::add(d, idx);
    if (Succeeded(rst))
      d->parent_ = this;
    return rst;
  }
  GameObject* GameObject::get(const char *id, bool create, const char *type, Uint32 idx) {
    if (!id || !*id) return 0;
    auto g = ContainerT<GameObject>::get(id);
    if (g) return g;
    if (!create) return 0;
    return create_gobj(type, id, idx);
  }

  ////////////////////////
  Result Layer::enter(Font &f, Texture &t, Audio &a) {
    Result rst = Result::S_OK;
    for (auto v : datas_) {
      if (v->load_res_on_enter())
        rst = v->load_resource(f, t, a);
      if (Failed(rst)) break;
      
      rst = v->enter(f, t, a);
      if (Failed(rst)) break;
    }
    return rst;
  }
  Result Layer::exit(Font &f, Texture &t, Audio &a) {
    Result rst = Result::S_OK;
    for (auto v : datas_) {
      rst = v->exit(f, t, a);
      if (Failed(rst)) break;

      if (v->load_res_on_enter())
        v->unload_resource(f, t, a);
    }
    return rst;
  }
  void Layer::update(Input &inp, Audio &aud) {
    if (!enabled_) return;
    for (auto v : datas_) {
      v->update(inp, aud);
    }
  }
  void Layer::render(Renderer &r) {
    if (!visible_) return;
    for (auto v : datas_) {
      v->render(r);
    }
  }
  GameObject* Layer::find_gobj(const char *id, bool child_first, bool order_rbegin) const {
    if (!id || !*id) return 0;
    if (order_rbegin) {
      for (auto it = datas_.rbegin(); it != datas_.rend(); --it) {
        if (!*it) continue;
        auto g = (*it)->find_gobj(id, child_first);
        if (g) return g;
      }
    } else {
      for (auto it = datas_.begin(); it != datas_.end(); ++it) {
        if (!*it) continue;
        auto g = (*it)->find_gobj(id, child_first);
        if (g) return g;
      }
    }
    return 0;
  }
  GameObject* Layer::create_gobj(const char *type, const char *id, Uint32 idx, bool load_res_on_enter) {
    if (!id) return 0;
    auto g = ContainerT<GameObject>::get(id);
    if (g) return g;
    g = g2d_->create_gobj(type, load_res_on_enter);
    if (!g) return 0;
    auto rst = g->init(g2d_, id, this);
    if (Succeeded(rst))
      rst = ContainerT<GameObject>::add(g, idx);
    g->decref();
    return g;
  }
  GameObject* Layer::get(const char *id, bool create, const char *type, Uint32 idx) {
    if (!id || !*id) return 0;
    auto g = ContainerT<GameObject>::get(id);
    if (g) return g;
    if (!create) return 0;
    return create_gobj(type, id, idx);
  }

  ////////////////////////
  Result Scene::enter(Font &f, Texture &t, Audio &a) {
    Result rst = Result::S_OK;
    for (auto v : datas_) {
      rst = v->enter(f, t, a);
      if (Failed(rst)) break;
    }
    return rst;
  }
  Result Scene::exit(Font &f, Texture &t, Audio &a) {
    Result rst = Result::S_OK;
    for (auto v : datas_) {
      rst = v->exit(f, t, a);
      if (Failed(rst)) break;
    }
    return rst;
  }
  void Scene::update(Input &inp, Audio &aud) {
    if (!enabled_) return;
    for (auto v : datas_) {
      v->update(inp, aud);
    }
  }
  void Scene::render(Renderer &r)  {
    if (!visible_) return;
    for (auto v : datas_) {
      v->render(r);
    }
  }
  GameObject* Scene::find_gobj(const char *id, bool child_first, bool order_rbegin) const {
    if (!id || !*id) return 0;
    if (order_rbegin) {
      for (auto it = datas_.rbegin(); it != datas_.rend(); --it) {
        if (!*it) continue;
        auto g = (*it)->find_gobj(id, child_first);
        if (g) return g;
      }
    } else {
      for (auto it = datas_.begin(); it != datas_.end(); ++it) {
        if (!*it) continue;
        auto g = (*it)->find_gobj(id, child_first);
        if (g) return g;
      }
    }
    return 0;
  }
  Layer* Scene::create_layer(const char *id, Uint32 idx) {
    if (!id || !*id) return 0;
    auto l = ContainerT<Layer>::get(id);
    if (l) return l;
    l = new Layer;
    auto rst = l->init(g2d_, id);
    if (Succeeded(rst))
      rst = ContainerT<Layer>::add(l, idx);
    l->decref();
    return l;
  }
  Layer* Scene::get(const char *id, bool create, Uint32 idx) {
    if (!id || !*id) return 0;
    auto l = ContainerT<Layer>::get(id);
    if (l) return l;
    if (!create) return 0;
    return create_layer(id, idx);
  }

  ////////////////////////
  void Game2D::uninit() {
    clear_scene();
    Game::uninit();
  }
  Scene* Game2D::load_scene(const char *id) {
    if (!id || !*id) return 0;
    auto it = scenes_.find(id);
    if (it != scenes_.end())
      return it->second;
    auto s = new Scene;
    if (Succeeded(s->init(this, id))) {
      scenes_[id] = s;
      return s;
    }
    s->decref();
    return 0;
  }
  void Game2D::unload_scene(const char *id) {
    if (!id || !*id) return;
    auto it = scenes_.find(id);
    if (it == scenes_.end()) return;
    if (current_scene_ == it->second)
      change_scene((Scene*)0);
    it->second->decref();
    scenes_.erase(it);
  }
  void Game2D::unload_scene(const Scene *scene) {
    if (!scene) return;
    for (auto it = scenes_.begin(); it != scenes_.end(); ++it) {
      if (it->second == scene) {
        if (current_scene_ == it->second)
          change_scene((Scene*)0);
        it->second->decref();
        scenes_.erase(it);
        break;
      }
    }
  }
  void Game2D::clear_scene() {
    change_scene((Scene*)0);
    for (auto &v : scenes_) {
      v.second->decref();
    }
    scenes_.clear();
  }
  Uint32 Game2D::scene_count() const { return scenes_.size(); }
  Scene* Game2D::get_scene(const char *id, bool create) {
    if (!id || !*id) return 0;
    auto it = scenes_.find(id);
    if (it != scenes_.end()) return it->second;
    if (!create) return 0;
    return load_scene(id);
  }
  Layer* Game2D::get_layer(const char *scene, const char *layer, bool create) {
    auto s = get_scene(scene, create);
    if (!s) return 0;
    return s->get(layer, create);
  }

  Result Game2D::change_scene(const char *id) {
    auto s = get_scene(id, false);
    if (!s) return Result::E_Fail;
    return change_scene(s);
  }
  Result Game2D::change_scene(Scene *scene) {
    if (scene == current_scene_) return Result::S_Existed;
    Result rst = Result::S_OK;
    if (current_scene_)
      rst = current_scene_->exit(font_, texture_, audio_);
    if (scene) {
      if (Succeeded(rst))
        rst = scene->enter(font_, texture_, audio_);
      if (Succeeded(rst))
        current_scene_ = scene;
    } else {
      current_scene_ = 0;
    }
    return rst;
  }
  Scene* Game2D::current_scene() const { return current_scene_; }
  GameObject* Game2D::create_gobj(const char *type, bool load_res_on_enter) const {
    if (!type || !*type) return 0;
    auto gobj = create_internal_object(type, load_res_on_enter);
    if (gobj) return gobj;
    if (fun_create_gobj_)
      return fun_create_gobj_(type, load_res_on_enter);
    return 0;
  }
  void Game2D::update(Input &inp, Audio &aud) {
    if (!current_scene_) return;
    current_scene_->update(inp, aud);
  }
  void Game2D::render(Renderer &r) {
    if (!current_scene_) return;
    current_scene_->render(r);
  }

  Result Game2D::load_res_file(const char *xml) {
    Builder builder(this);
    return builder.load_res_file(xml);
  }
  Result Game2D::load_res_buffer(const char *xml, Uint32 len) {
    Builder builder(this);
    return builder.load_res_buffer(xml, len);
  }
  Result Game2D::load_scene_file(const char *xml)  {
    Builder builder(this);
    return builder.load_scene_file(xml);
  }
  Result Game2D::load_scene_buffer(const char *xml, Uint32 len)  {
    Builder builder(this);
    return builder.load_scene_buffer(xml, len);
  }
} // end namespace g2d